﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr.Runtime;
using System.Collections;
using PSLib.Parsing.Common;
using PSLib.Parsing.Lua.ObjectModel;

namespace PSLib.Parsing.Lua.ObjectModel
{
    public class Scope
    {
        #region Constructor

        internal Scope(string displayName, Scope parent, ObjectModelProvider omProvider)
        {
            this.environmentID = Guid.NewGuid();
            this.parentEnvironmentID = parent != null ? parent.environmentID : Guid.Empty;
            this.childEnvironments = new List<Guid>();
            this.variables = new Dictionary<string, Variable>();

            this.Span = new ElementSpan();
            this.Name = displayName;
            
            this.ObjectModel = omProvider;
            
            if (parent != null) parent.childEnvironments.Add(this.environmentID);
            this.ObjectModel.Scopes.Add(this.environmentID, this);
        }

        #endregion

        #region Overrides

        public override string ToString()
        {
            string result = "Unnamed";
            if (!string.IsNullOrEmpty(this.Name)) result = this.Name;
            return result;
        }

        #endregion

        #region Public methods

        public void AddVariable(Variable variable)
        {
            if (!this.variables.ContainsKey(variable.Name) && 
                variable.Name != "_" && 
                variable.Name != Variable.AutoAsignName &&
                variable.Name != Variable.UnknownIndexName)
            {
                this.variables.Add(variable.Name, variable);
            }
        }

        public Variable GetVariable(string name, bool getCurrentOnly = false, bool addIfNotExists = true)
        {
            Variable result = null;

            if (!this.variables.ContainsKey(name))
            {
                if (!getCurrentOnly)
                {
                    if(this.Parent != null) result = this.Parent.GetVariable(name, false, false);
                    else result = this.ObjectModel.GetGlobalVariable(name);
                }
            }
            else result = this.variables[name];
            if (result == null && addIfNotExists)
            {
                result = new Variable() { Name = name };
                this.AddVariable(result);
            }
            return result;
        }

        public IList<Variable> GetVariables(ElementPosition position, bool getCurrentOnly = false)
        {
            List<Variable> variables = new List<Variable>();
            variables.AddRange(this.Variables.Where(v => v.GetValue(position, false) != null));
            if (!getCurrentOnly && this.Parent != null) variables.AddRange(this.Parent.GetVariables(position, getCurrentOnly));
            return variables;
        }

        #endregion

        #region Properties

        public Scope Parent
        {
            get
            { 
                Scope result = null;
                if (this.ObjectModel.Scopes.ContainsKey(this.parentEnvironmentID)) result = this.ObjectModel.Scopes[this.parentEnvironmentID];
                return result;
            }
        }
        public IEnumerable<Scope> ChildScopes
        {
            get 
            {
                foreach (var item in childEnvironments)
                {
                    if (this.ObjectModel.Scopes.ContainsKey(item))
                        yield return this.ObjectModel.Scopes[item];
                }
            }
        }

        public ElementSpan Span { get; private set; }
        public string Name { get; private set; }

        public ObjectModelProvider ObjectModel { get; private set; }

        public IList<Variable> Variables { get { return variables.Values.ToList(); } }
        #endregion
        
        private Guid environmentID;
        private Guid parentEnvironmentID;
        private List<Guid> childEnvironments;

        private Dictionary<string, Variable> variables;
    }
}
